/*
 * Copyright (C) 2010 Apple Inc. All rights reserved.
 * Copyright (C) 2012 Sony Computer Entertainment Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "WebView.h"

#include "DrawingAreaProxyImpl.h"
#include "NativeWebKeyboardEvent.h"
#include "NativeWebMouseEvent.h"
#include "NativeWebWheelEvent.h"
#include "PageClientImpl.h"
#include "WebContext.h"
#include "WebPageCreationParameters.h"
#include "WebPopupMenuItemManx.h"
#include <WebCore/Editor.h>
#include <WebCore/IntRect.h>
#include <WebCore/Region.h>
#include <cairo.h>
#include <manx/KeyboardCodes.h>

using namespace WebCore;

namespace WebKit {

WebView::WebView(WebContext* context, WebPageGroup* pageGroup, const WKViewClient* client)
    : m_pageClient(this)
    , m_popupMenu(0)
    , m_isFocused(false)
    , m_isActive(true)
    , m_isVisible(false)
{
    m_viewClient.initialize(client);
    m_page = context->createWebPage(&m_pageClient, pageGroup);
    m_page->initializeWebPage();

#if ENABLE(FULLSCREEN_API)
    m_isFullScreen = false;
#endif
}

WebView::~WebView()
{
}

void WebView::initialize()
{
#if ENABLE(FULLSCREEN_API)
    m_page->fullScreenManager()->setWebView(this);
#endif
}

static void drawPageBackground(cairo_t* ctx, const WebPageProxy* page, const WebCore::IntRect& rect)
{
    if (!page->drawsBackground() || page->drawsTransparentBackground())
        return;
    cairo_set_source_rgba(ctx, 0.5, 0.5, 0.5, 1.0);
    cairo_rectangle(ctx, rect.x(), rect.y(), rect.width(), rect.height());
    cairo_set_operator(ctx, CAIRO_OPERATOR_OVER);
    cairo_fill(ctx);
}

void WebView::paint(cairo_t* ctx, const WebCore::IntRect& dirtyRect)
{
    m_page->endPrinting();
    if (DrawingAreaProxyImpl* drawingArea = static_cast<DrawingAreaProxyImpl*>(m_page->drawingArea())) {
        // FIXME: We should port WebKit1's rect coalescing logic here.
        Region unpaintedRegion;
        drawingArea->paint(ctx, dirtyRect, unpaintedRegion);

        Vector<IntRect> unpaintedRects = unpaintedRegion.rects();
        for (size_t i = 0; i < unpaintedRects.size(); ++i)
            drawPageBackground(ctx, m_page.get(), unpaintedRects[i]);
    } else
        drawPageBackground(ctx, m_page.get(), dirtyRect);

    m_page->didDraw();
}

void WebView::setViewNeedsDisplay(const WebCore::IntRect& rect)
{
    m_viewClient.setViewNeedsDisplay(this, rect);
}

void WebView::scrollBy(int x, int y)
{
    if (x)
        m_page->scrollBy(x > 0 ? ScrollRight : ScrollLeft, ScrollByLine);
    if (y)
        m_page->scrollBy(y > 0 ? ScrollDown : ScrollUp, ScrollByLine);
}

void WebView::handleKeyboardEvent(const Manx::KeyboardEvent& event)
{
    NativeWebKeyboardEvent nativeEvent(event);

    if ((nativeEvent.type() == WebEvent::KeyDown) && ((nativeEvent.keyIdentifier() == "Accept") || (nativeEvent.keyIdentifier() == "Convert") || (nativeEvent.keyIdentifier() == "Cancel")))
        m_page->handleKeyboardEventManx(nativeEvent, event.m_caretOffset);

    else if ((nativeEvent.type() == WebEvent::RawKeyDown) && (nativeEvent.keyIdentifier() == "TabJump"))
        m_page->handleKeyboardEventManx(nativeEvent, event.m_caretOffset);

    else
        m_page->handleKeyboardEvent(nativeEvent);
}

void WebView::handleImeEvent(const Manx::ImeEvent& event)
{
    WTF::String compositionString = WTF::String::fromUTF8(event.m_compositionString);

    if (event.m_imeEventType == Manx::ImeEvent::UpdatePreedit) {
        Vector<CompositionUnderline> underlines;
        underlines.append(CompositionUnderline(0, compositionString.length(), Color(Color::black), false));
        m_page->setComposition(compositionString, underlines, event.m_caretIndex, event.m_caretIndex, 0, 0);
        return;
    }

    if ((event.m_imeEventType == Manx::ImeEvent::ConfirmPreedit) || (event.m_imeEventType == Manx::ImeEvent::InsertText)) {
        m_page->confirmComposition(compositionString, event.m_caretIndex, 0);
        return;
    }

    if (event.m_imeEventType == Manx::ImeEvent::SetValueToFocusedNode)
        m_page->setValueToFocusedNode(compositionString);
}

void WebView::clearSelection()
{
    m_page->clearSelectionWithoutBlur();
}

void WebView::handleMouseEvent(const Manx::MouseEvent& mouseEvent)
{
    m_page->handleMouseEvent(NativeWebMouseEvent(mouseEvent));
}

void WebView::handleWheelEvent(const Manx::WheelEvent& wheelEvent)
{
    m_page->handleWheelEvent(NativeWebWheelEvent(wheelEvent));
}

WebCore::IntSize WebView::viewSize()
{
    return m_viewClient.viewSize(this);
}

void WebView::setSize(int width, int height)
{
    if (m_page->drawingArea())
        m_page->drawingArea()->setSize(IntSize(width, height), IntSize(), IntSize());
}

void WebView::setPopupMenuClient(const WKViewPopupMenuClient* client)
{
    m_popupMenuClient.initialize(client);
}

void WebView::showPopupMenu(WebPopupMenuProxyManx* popupMenu, const Vector<WebPopupItem>& items, const WebCore::IntRect& rect, int32_t selectedIndex)
{
    m_popupMenu = popupMenu;
    m_popupMenuClient.showPopupMenu(this, items, rect, selectedIndex);
    return;
}

void WebView::hidePopupMenu()
{
    m_popupMenuClient.hidePopupMenu(this);
    m_popupMenu = 0;
}

void WebView::valueChangedForPopupMenu(int selectedIndex)
{
    if (m_popupMenu)
        m_popupMenu->valueChangedForPopupMenu(selectedIndex);
}

#if ENABLE(MANX_CURSOR_NAVIGATION)
void WebView::setCursorPosition(const WebCore::IntPoint& cursorPosition)
{
    m_viewClient.setCursorPosition(this, cursorPosition);
}
#endif

void WebView::setFocused(bool isFocused)
{
    m_isFocused = isFocused;
    m_page->viewStateDidChange(WebPageProxy::ViewIsFocused);
}

void WebView::setActive(bool isActive)
{
    m_isActive = isActive;
    m_page->viewStateDidChange(WebPageProxy::ViewWindowIsActive);
}

void WebView::setIsVisible(bool isVisible)
{
    m_isVisible = isVisible;

    if (m_page)
        m_page->viewStateDidChange(WebPageProxy::ViewIsVisible);
}

void WebView::runJavaScriptAlert(WebFrameProxy* frame, const String& message, PassRefPtr<Messages::WebPageProxy::RunJavaScriptAlert::DelayedReply> reply)
{    
    m_alertReply = reply;
    m_viewClient.runJavaScriptAlert(this, message, frame);
}

void WebView::runJavaScriptComfirm(WebFrameProxy* frame, const String& message,  PassRefPtr<Messages::WebPageProxy::RunJavaScriptConfirm::DelayedReply> reply)
{
    m_confirmReply = reply;
    m_viewClient.runJavaScriptConfirm(this, message, frame);
}

void WebView::runJavaScriptPrompt(WebFrameProxy* frame, const String& message, const String& defaultValue, PassRefPtr<Messages::WebPageProxy::RunJavaScriptPrompt::DelayedReply> reply)
{
    m_promptReply = reply;
    m_viewClient.runJavaScriptPrompt(this, message, defaultValue, frame);
}

void WebView::handleAuthenticationRequiredRequest(WebFrameProxy* frame, const String& hostname, const String& realm, const String& prefilledUsername, const String& prefilledPassword, PassRefPtr<Messages::WebPageProxy::AuthenticationRequiredRequest::DelayedReply> reply)
{
    m_authReply = reply;
    m_viewClient.authenticationRequiredRequest(this, hostname, realm, prefilledUsername, prefilledPassword, frame);
}

void WebView::handleCertificateVerificationRequest(WebFrameProxy* frame, uint32_t error, const String& url, const Vector<CString>& certificates, PassRefPtr<Messages::WebPageProxy::CertificateVerificationRequest::DelayedReply> reply)
{
    m_certReply = reply;
    m_viewClient.certificateVerificationRequest(this, error, url, certificates, frame);
}

void WebView::createNewPage(WebPageProxy* page, const WebCore::ResourceRequest& request, const WebCore::WindowFeatures& features, WebEvent::Modifiers modifiers, WebMouseEvent::Button button, PassRefPtr<Messages::WebPageProxy::CreateNewPage::DelayedReply> reply)
{
    m_createNewPageReply = reply;
    m_viewClient.createNewPage(this, request, features, modifiers, button);
}

void WebView::replyJavaScriptAlert()
{
    if (m_alertReply) {
        m_alertReply->send();
        m_alertReply = 0;
    }
}

void WebView::replyJavaScriptConfirm(bool confirmResult)
{
    if (m_confirmReply) {
        m_confirmReply->send(confirmResult);
        m_confirmReply = 0;
    }
}

void WebView::replyJavaScriptPrompt(const String& promptResult)
{
    if (m_promptReply) {
        m_promptReply->send(promptResult);
        m_promptReply = 0;
    }
}

void WebView::replyAuthenticationChallenge(bool result, const String& username, const String& password)
{
    if (m_authReply) {
        m_authReply->send(result, username, password);
        m_authReply = 0;
    }
}

void WebView::replyCertificateVerificationRequest(bool result)
{
    if (m_certReply) {
        m_certReply->send(result);
        m_certReply = 0;
    }
}

void WebView::replyCreateNewPage(PassRefPtr<WebPageProxy> page)
{
    if (m_createNewPageReply) {
        RefPtr<WebPageProxy> newPage = page;
        uint64_t newPageID = 0;
        WebPageCreationParameters newPageParameters;
        if (newPage) {
            newPageID = newPage->pageID();
            newPageParameters = newPage->creationParameters();
        }
        m_createNewPageReply->send(newPageID, newPageParameters);
        m_createNewPageReply = 0;
    }
}

void WebView::didChangeEditorState(int eventType, const WebKit::EditorState& editorState)
{
    m_viewClient.didChangeEditorState(this, eventType, editorState);
}

void WebView::didChangeCompositionState(const WebCore::IntRect& compositionRect)
{
    m_viewClient.didChangeCompositionState(this, compositionRect);
}

void WebView::didChangeSelectionState(const String& selectedText, const WebCore::IntRect& selectedRect)
{
    m_viewClient.didChangeSelectionState(this, selectedText, selectedRect);
}

void WebView::setCursor(const WebCore::Cursor& cursor)
{
    m_viewClient.setCursor(this, cursor);
}

#if ENABLE(CONTEXT_MENUS)
void WebView::setContextMenuClient(const WKViewContextMenuClient* client)
{
    m_contextMenuClient.initialize(client);
}

void WebView::showContextMenu(const WebCore::IntPoint& point, const Vector<WebContextMenuItemData>& items)
{
    m_contextMenuClient.showContextMenu(this, point, items);
}

void WebView::hideContextMenu()
{
    m_contextMenuClient.hideContextMenu(this);
}

void WebView::contextMenuItemSelected(WebContextMenuItemData* item)
{
    if (item)
        m_page->contextMenuItemSelected(*item);
}
#endif // ENABLE(CONTEXT_MENUS)

void WebView::didRelaunchProcess()
{
#if ENABLE(FULLSCREEN_API)
    m_page->fullScreenManager()->setWebView(this);
#endif
}

void WebView::doneWithKeyEvent(const NativeWebKeyboardEvent& event, bool wasEventHandled)
{
    m_viewClient.doneWithKeyEvent(this, event, wasEventHandled);
}

void WebView::doneWithMouseDownEvent(bool wasEventHandled)
{
    m_viewClient.doneWithMouseDownEvent(this, wasEventHandled);
}

void WebView::doneWithMouseUpEvent(bool wasEventHandled)
{
    m_viewClient.doneWithMouseUpEvent(this, wasEventHandled);
}

#if ENABLE(FULLSCREEN_API)
void WebView::invalidateFullScreen()
{
    WebView::closeFullScreen();
}

bool WebView::isFullScreen()
{
    return m_isFullScreen;
}

void WebView::closeFullScreen()
{
    if (m_isFullScreen)
        m_viewClient.closeFullScreen(this);
    m_isFullScreen = false;
}

void WebView::enterFullScreen()
{
    if (!m_isFullScreen)
        m_viewClient.enterFullScreen(this);
}

void WebView::exitFullScreen()
{
    if (m_isFullScreen)
        m_viewClient.exitFullScreen(this);
}

void WebView::beganEnterFullScreen(const WebCore::IntRect& initialFrame, const WebCore::IntRect& finalFrame)
{
    m_viewClient.beganEnterFullScreen(this, initialFrame, finalFrame);
}

void WebView::beganExitFullScreen(const WebCore::IntRect& initialFrame, const WebCore::IntRect& finalFrame)
{
    m_viewClient.beganExitFullScreen(this, initialFrame, finalFrame);
}

void WebView::willEnterFullScreen()
{
    m_isFullScreen = true;
    m_page->fullScreenManager()->willEnterFullScreen();
}

void WebView::didEnterFullScreen()
{
    m_page->fullScreenManager()->didEnterFullScreen();
}

void WebView::willExitFullScreen()
{
    m_isFullScreen = false;
    m_page->fullScreenManager()->willExitFullScreen();
}

void WebView::didExitFullScreen()
{
    m_page->fullScreenManager()->didExitFullScreen();
}

void WebView::requestExitFullScreen()
{
    if (m_isFullScreen)
        m_page->fullScreenManager()->requestExitFullScreen();
}

#endif // ENABLE(FULLSCREEN_API)

#if USE(ACCELERATED_COMPOSITING)
void WebView::enterAcceleratedCompositingMode(uint32_t canvasHandle)
{
    m_viewClient.enterAcceleratedCompositingMode(this, canvasHandle);
}

void WebView::exitAcceleratedCompositingMode()
{
    m_viewClient.exitAcceleratedCompositingMode(this);
}

void WebView::updateAcceleratedCompositingMode(uint32_t canvasHandle)
{
    m_viewClient.updateAcceleratedCompositingMode(this, canvasHandle);
}
#endif // USE(ACCELERATED_COMPOSITING)

}
